// DOM-IGNORE-BEGIN
/*******************************************************************************
* Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries.
*
* Subject to your compliance with these terms, you may use Microchip software
* and any derivatives exclusively with Microchip products. It is your
* responsibility to comply with third party license terms applicable to your
* use of third party software (including open source software) that may
* accompany Microchip software.
*
* THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
* EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
* WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
* PARTICULAR PURPOSE.
*
* IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE,
* INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND
* WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS
* BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE
* FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN
* ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
* THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
*******************************************************************************/
// DOM-IGNORE-END


#include "gfx/legato/widget/circularslider/legato_widget_circular_slider.h"

#if LE_CIRCULARSLIDER_WIDGET_ENABLED == 1

#include "gfx/legato/common/legato_error.h"
#include "gfx/legato/common/legato_math.h"
#include "gfx/legato/common/legato_utils.h"
#include "gfx/legato/memory/legato_memory.h"
#include "gfx/legato/string/legato_string.h"
#include "gfx/legato/widget/legato_widget.h"

#include <math.h>

#define DEFAULT_WIDTH           100
#define DEFAULT_HEIGHT          100

#define DEFAULT_RADIUS          30
#define DEFAULT_START_ANGLE     0
#define DEFAULT_SPAN_ANGLE      180
#define DEFAULT_VALUE           0

#define DEFAULT_DIRECTION       COUNTER_CLOCKWISE

#define DEFAULT_OUTSIDE_BORDER_THICKNESS    5
#define DEFAULT_INSIDE_BORDER_THICKNESS     5
#define DEFAULT_ACTIVE_ARC_THICKNESS        30
#define DEFAULT_SLIDER_BUTTON_THICKNESS     3
#define DEFAULT_SLIDER_BUTTON_RADIUS        15

//static void touchDown(leCircularSliderWidget* _this, leInput_TouchDownEvent* evt);
//static void touchUp(leCircularSliderWidget* _this, leInput_TouchUpEvent* evt);
//static void touchMoved(leCircularSliderWidget* _this, leInput_TouchMoveEvent* evt);

static
#if LE_DYNAMIC_VTABLES == 0
const
#endif
leCircularSliderWidgetVTable circularSliderWidgetVTable;

static uint32_t _calculateSnapValue(uint32_t divisions,
                                    uint32_t val)
{
    uint32_t clmp;

    if(divisions > 1)
    {
        uint32_t stepAmt = 100 / divisions;

        for(uint32_t lastStep = 0; lastStep < 100; lastStep += stepAmt)
        {
            clmp = lastStep + stepAmt;

            if(clmp > 100)
            {
                clmp = 100;
            }

            if(val < clmp)
            {
                if(val - lastStep < (clmp) - val)
                {
                    return lastStep;
                }
                else
                {
                    return clmp;
                }
            }
            else if(val == stepAmt)
            {
                return stepAmt;
            }
        }

        return 100;
    }
    else
    {
        if(val <= 50)
        {
            return 0;
        }
        else
        {
            return 100;
        }
    }
}

void leCircularSliderWidget_Constructor(leCircularSliderWidget* _this)
{
    leWidget_Constructor((leWidget*)_this);
    
    _this->widget.fn = (void*)&circularSliderWidgetVTable;
    _this->fn = &circularSliderWidgetVTable;

    _this->widget.type = LE_WIDGET_CIRCULAR_SLIDER;
    
    _this->widget.rect.width = DEFAULT_WIDTH;
    _this->widget.rect.height = DEFAULT_HEIGHT;

    _this->widget.style.borderType = LE_WIDGET_BORDER_NONE;
    _this->widget.style.backgroundType = LE_WIDGET_BACKGROUND_NONE;

    _this->radius = DEFAULT_RADIUS;
    _this->startAngle = DEFAULT_START_ANGLE;
    _this->spanAngle = DEFAULT_SPAN_ANGLE;
    _this->value = DEFAULT_VALUE;

    _this->direction = LE_COUNTER_CLOCKWISE;
    _this->roundEdges = LE_TRUE;
    _this->sticky = LE_FALSE;
    _this->snapDivisions = 1;
    _this->buttonTouch = LE_TRUE;
    _this->btnState = LE_CIRCULAR_SLIDER_STATE_UP;

    _this->outsideBorderArc.visible = LE_TRUE;
    _this->outsideBorderArc.thickness = DEFAULT_OUTSIDE_BORDER_THICKNESS;
    _this->outsideBorderArc.scheme = _this->widget.scheme;

    _this->activeArc.visible = LE_TRUE;
    _this->activeArc.radius = _this->radius - _this->outsideBorderArc.thickness;
    _this->activeArc.thickness = DEFAULT_ACTIVE_ARC_THICKNESS;
    _this->activeArc.scheme = _this->widget.scheme;

    _this->inActiveArc.visible = LE_TRUE;
    _this->inActiveArc.radius = _this->activeArc.radius;
    _this->inActiveArc.thickness = _this->activeArc.thickness;
    _this->inActiveArc.scheme = _this->widget.scheme;

    _this->insideBorderArc.visible = LE_TRUE;
    _this->insideBorderArc.radius = _this->activeArc.radius - _this->activeArc.thickness;
    _this->insideBorderArc.thickness = DEFAULT_INSIDE_BORDER_THICKNESS;
    _this->insideBorderArc.scheme = _this->widget.scheme;

    _this->circleButtonArc.visible = LE_TRUE;
    _this->circleButtonArc.radius = DEFAULT_SLIDER_BUTTON_RADIUS;
    _this->circleButtonArc.thickness = DEFAULT_SLIDER_BUTTON_THICKNESS;
    _this->circleButtonArc.scheme = _this->widget.scheme;

    _this->pressedCallback = NULL;
    _this->valueChangedCallback = NULL;
    _this->releasedCallback = NULL;
}

void _leWidget_Destructor(leWidget* _this);

void _leCircularSliderWidget_Destructor(leCircularSliderWidget* _this)
{
    _leWidget_Destructor((leWidget*)_this);
}

leCircularSliderWidget* leCircularSliderWidget_New()
{
    leCircularSliderWidget* slider = NULL;

    slider = LE_MALLOC(sizeof(leCircularSliderWidget));

    if(slider == NULL)
        return NULL;
    
    leCircularSliderWidget_Constructor(slider);

    return slider;
}

static uint32_t getRadius(const leCircularSliderWidget* _this)
{
    LE_ASSERT_THIS();
        
    return _this->radius;
}

static leResult setRadius(leCircularSliderWidget* _this,
                          uint32_t rad)
{
    LE_ASSERT_THIS();
        
    if(_this->radius == rad)
        return LE_SUCCESS;
        
    _this->radius = rad;
    
    _this->fn->invalidate(_this);
        
    return LE_SUCCESS;
}

static int32_t getStartAngle(const leCircularSliderWidget* _this)
{
    LE_ASSERT_THIS();
        
    return _this->startAngle;
}

static leResult setStartAngle(leCircularSliderWidget* _this,
                              int32_t angle)
{
    LE_ASSERT_THIS();
        
    if(_this->startAngle == angle)
        return LE_SUCCESS;

    if (angle > 360)
    {
        angle = 360;
    }
    else if(angle < -360)
    {
        angle = -360;
    }

    _this->startAngle = angle;
    
    _this->fn->invalidate(_this);
        
    return LE_SUCCESS;
}

static int32_t getSpanAngle(const leCircularSliderWidget* _this)
{
    LE_ASSERT_THIS();

    return _this->spanAngle;
}

static leResult setSpanAngle(leCircularSliderWidget* _this,
                             int32_t angle)
{
    LE_ASSERT_THIS();

    if(_this->spanAngle == angle)
        return LE_SUCCESS;

    if (angle > 360)
    {
        angle = 360;
    }
    else if(angle < -360)
    {
        angle = -360;
    }

    _this->spanAngle = angle;

    _this->fn->invalidate(_this);

    return LE_SUCCESS;
}

static uint32_t getValue(const leCircularSliderWidget* _this)
{
    LE_ASSERT_THIS();
        
    return _this->value;
}

static leResult setValue(leCircularSliderWidget* _this,
                         uint32_t value)
{
    LE_ASSERT_THIS();
        
    if(_this->value == value)
        return LE_SUCCESS;

    if (value > 100)
        return LE_FAILURE;
    
    _this->fn->invalidate(_this);

    _this->value = value;
    
    if (_this->valueChangedCallback != NULL)
    {
        _this->valueChangedCallback(_this, _this->value);
    }
        
    return LE_SUCCESS;
}

static leBool getRoundEdges(const leCircularSliderWidget* _this)
{
    LE_ASSERT_THIS();
    
    return _this->roundEdges;
}

static leResult setRoundEdges(leCircularSliderWidget* _this,
                              leBool round)
{
    LE_ASSERT_THIS();
        
    if(_this->roundEdges == round)
        return LE_SUCCESS;
        
    _this->roundEdges = round;
    
    _this->fn->invalidate(_this);
        
    return LE_SUCCESS;
}

static leBool getStickyButton(const leCircularSliderWidget* _this)
{
    LE_ASSERT_THIS();
        
    return _this->sticky;
}

static leResult setStickyButton(leCircularSliderWidget* _this,
                                leBool sticky)
{
    LE_ASSERT_THIS();
        
    if(_this->sticky == sticky)
        return LE_SUCCESS;
        
    _this->sticky = sticky;

    if(_this->sticky == LE_TRUE)
    {
        _this->fn->setValue(_this,
                            _calculateSnapValue(_this->snapDivisions,
                                                _this->value));
    }
        
    return LE_SUCCESS;
}

static uint32_t getSnapDivisions(const leCircularSliderWidget* _this)
{
    LE_ASSERT_THIS();

    return _this->sticky;
}

static leResult setSnapDivisions(leCircularSliderWidget* _this,
                                 uint32_t div)
{
    LE_ASSERT_THIS();

    if(_this->snapDivisions == div)
        return LE_SUCCESS;

    if(_this->snapDivisions == 0)
        return LE_FAILURE;

    _this->snapDivisions = div;

    if(_this->sticky == LE_TRUE)
    {
        _this->fn->setValue(_this,
                            _calculateSnapValue(_this->snapDivisions,
                                                _this->value));
    }

    return LE_SUCCESS;
}

static leBool getTouchOnButtonOnly(const leCircularSliderWidget* _this)
{
    LE_ASSERT_THIS();
        
    return _this->buttonTouch;
}

static leResult setTouchOnButtonOnly(leCircularSliderWidget* _this,
                                     leBool buttonOnly)
{
    LE_ASSERT_THIS();

    if(_this->buttonTouch == buttonOnly)
        return LE_SUCCESS;
        
    _this->buttonTouch = buttonOnly;
    
    //_this->fn->invalidate(_this);
        
    return LE_SUCCESS;
}


static uint32_t getArcThickness(const leCircularSliderWidget* _this, 
                                leCircularSliderWidgetArcType type)
{
    LE_ASSERT_THIS();
    
    switch(type)
    {
        case OUTSIDE_CIRCLE_BORDER:
            return _this->outsideBorderArc.thickness;
        case INSIDE_CIRCLE_BORDER:
            return _this->insideBorderArc.thickness;
        case ACTIVE_AREA:
            return _this->activeArc.thickness;
        case INACTIVE_AREA:
            return _this->inActiveArc.thickness;
        case CIRCLE_BUTTON:
            return _this->circleButtonArc.thickness;
        default:
            break;
    }
        
    return 0;
}

static leResult setArcThickness(leCircularSliderWidget* _this, 
                                leCircularSliderWidgetArcType type,
                                uint32_t thickness)
{
    LE_ASSERT_THIS();

    switch(type)
    {
        case OUTSIDE_CIRCLE_BORDER:
        {
            //if (thickness == _this->outsideBorderArc.thickness)
            //    return LE_SUCCESS;

            if (thickness > _this->radius)
                return LE_FAILURE;
            
            if (thickness > (_this->radius - _this->activeArc.thickness - _this->insideBorderArc.thickness))
                return LE_FAILURE;

            _this->outsideBorderArc.thickness = thickness;

            break;
        }
        case ACTIVE_AREA:
        { 
            //if (thickness == _this->activeArc.thickness)
            //    return LE_SUCCESS;

            if (thickness > _this->radius - _this->outsideBorderArc.thickness)
                return LE_FAILURE;
            
            if (thickness > (_this->radius - _this->outsideBorderArc.thickness - _this->insideBorderArc.thickness))
                return LE_FAILURE;

            _this->activeArc.thickness = thickness;
            _this->inActiveArc.thickness = thickness;

            _this->insideBorderArc.radius = _this->activeArc.radius - _this->activeArc.thickness;

            break;
        }

        case INSIDE_CIRCLE_BORDER:
        {
            //if (thickness == _this->insideBorderArc.thickness)
            //    return LE_SUCCESS;

            if (thickness > _this->radius - _this->outsideBorderArc.thickness - _this->activeArc.thickness)
                return LE_FAILURE;
            
            _this->insideBorderArc.thickness = thickness;
        
            break;
        }
        case CIRCLE_BUTTON:
        {
            //if (thickness == _this->circleButtonArc.thickness)
            //    return LE_SUCCESS;

            if (thickness > _this->circleButtonArc.radius)
                return LE_FAILURE;

            _this->circleButtonArc.thickness = thickness;

            break;
        }
        default:
            return LE_FAILURE;
    }

    _this->fn->invalidate(_this);
        
    return LE_SUCCESS;
}

static uint32_t getArcRadius(const leCircularSliderWidget* _this,
                             leCircularSliderWidgetArcType type)
{
    LE_ASSERT_THIS();

    switch(type)
    {
        case CIRCLE_BUTTON:
            return _this->circleButtonArc.radius;
        default:
            break;
    }
        
    return 0;
}

static leResult setArcRadius(leCircularSliderWidget* _this,
                             leCircularSliderWidgetArcType type,
                             uint32_t rad)
{
    LE_ASSERT_THIS();

    switch(type)
    {
        case CIRCLE_BUTTON:
        {
            if (_this->circleButtonArc.radius == rad)
                return LE_SUCCESS;
            
            if (rad > _this->radius)
                return LE_FAILURE;

            _this->circleButtonArc.radius = rad;

            break;
        }
        default:
            return LE_FAILURE;
    }
        
    _this->fn->invalidate(_this);
        
    return LE_SUCCESS;
}

static leScheme* getArcScheme(const leCircularSliderWidget* _this,
                              leCircularSliderWidgetArcType type)
{
    LE_ASSERT_THIS();

    switch(type)
    {
        case CIRCLE_BUTTON:
            return (leScheme*)_this->circleButtonArc.scheme;
        default:
            return (leScheme*)_this->widget.scheme;
    }
        
    return NULL;
}

static leResult setArcScheme(leCircularSliderWidget* _this,
                             leCircularSliderWidgetArcType type,
                             const leScheme* scheme)
{
    LE_ASSERT_THIS();

    switch(type)
    {
        //Only the circle button has a different scheme from parent widget
        case CIRCLE_BUTTON:
            _this->circleButtonArc.scheme = scheme;
            break;
        default:
            return LE_FAILURE;
    }
        
    _this->fn->invalidate(_this);
        
    return LE_SUCCESS;
}

static leBool getArcVisible(const leCircularSliderWidget* _this,
                            leCircularSliderWidgetArcType type)
{
    LE_ASSERT_THIS();

    switch(type)
    {
        case OUTSIDE_CIRCLE_BORDER:
            return _this->outsideBorderArc.visible;
        case INSIDE_CIRCLE_BORDER:
            return _this->insideBorderArc.visible;
        case ACTIVE_AREA:
            return _this->activeArc.visible;
        case INACTIVE_AREA:
            return _this->inActiveArc.visible;
        case CIRCLE_BUTTON:
            return _this->circleButtonArc.visible;
        default:
            break;
    }
        
    return LE_FALSE;
}

static leResult setArcVisible(leCircularSliderWidget* _this,
                              leCircularSliderWidgetArcType type,
                              leBool visible)
{
    LE_ASSERT_THIS();

    switch(type)
    {
        case OUTSIDE_CIRCLE_BORDER:
        {
            if (_this->outsideBorderArc.visible == visible)
                return LE_SUCCESS;

            _this->outsideBorderArc.visible = visible;
            
            break;
        }
        case INSIDE_CIRCLE_BORDER:
        {
            if (_this->insideBorderArc.visible == visible)
                return LE_SUCCESS;

            _this->insideBorderArc.visible = visible;
            
            break;
        }
        case ACTIVE_AREA:
        {
            if (_this->activeArc.visible == visible)
                return LE_SUCCESS;

            _this->activeArc.visible = visible;
            
            break;
        }
        case INACTIVE_AREA:
        {
            if (_this->inActiveArc.visible == visible)
                return LE_SUCCESS;

            _this->inActiveArc.visible = visible;
            
            break;
        }
        case CIRCLE_BUTTON:
        {
            if (_this->circleButtonArc.visible == visible)
                return LE_SUCCESS;

            _this->circleButtonArc.visible = visible;
            
            break;
        }
        default:
            return LE_FAILURE;
    }
        
    _this->fn->invalidate(_this);
        
    return LE_SUCCESS;
}

static leRotationDirection getDirection(const leCircularSliderWidget* _this)
{
    LE_ASSERT_THIS();
        
    return _this->direction;
}

static leResult setDirection(leCircularSliderWidget* _this,
                             leRotationDirection dir)
{
    LE_ASSERT_THIS();
        
    if(_this->direction == dir)
        return LE_SUCCESS;
        
    _this->direction = dir;
    
    _this->fn->invalidate(_this);
        
    return LE_SUCCESS;
}

static leResult setPressedEventCallback(leCircularSliderWidget* _this,
                                        leCircularSliderWidget_PressedEvent cb)
{
    LE_ASSERT_THIS();

    _this->pressedCallback = cb;
        
    return LE_SUCCESS;
}

static leResult setValueChangedEventCallback(leCircularSliderWidget* _this,
                                             leCircularSliderWidget_ValueChangedEvent cb)
{
    LE_ASSERT_THIS();

    _this->valueChangedCallback = cb;
        
    return LE_SUCCESS;
}

static leResult setReleasedEventCallback(leCircularSliderWidget* _this,
                                         leCircularSliderWidget_ReleasedEvent cb)
{
    LE_ASSERT_THIS();
        
    _this->releasedCallback = cb;

    return LE_SUCCESS;
}

static leBool getPercent(leCircularSliderWidget* _this,
                         lePoint pt,
                         uint32_t* percent)
{
    int32_t startAngle;
    int32_t endAngle;
    lePoint centerPt;
    float rad, mag, dot, det;
    int32_t deg, adj;
    float xf, yf;

    // transform the vector
    centerPt.x = _this->widget.rect.width / 2;
    centerPt.y = _this->widget.rect.height / 2;

    pt.x -= centerPt.x;
    pt.y -= centerPt.y;
    pt.y *= -1;

    // normalize the vector
    mag = leSqrt((pt.x * pt.x) + (pt.y * pt.y));

    xf = (float)pt.x / mag;
    yf = (float)pt.y / mag;

    // calculate the angle
    dot = (xf * 1.0f) + (yf * 0.0f);
    det = (xf * 0.0f) - (yf * 1.0f);

    rad = -atan2f(det, dot);

    deg = (int32_t)(rad * 57.295827909f);

    // normalize the angle
    if(deg < 0)
    {
        deg += 360;
    }

    // normalize the angles the angle span
    leNormalizeAngles(_this->startAngle,
                      _this->spanAngle,
                      &startAngle,
                      &endAngle);

    adj = 360 - startAngle;

    endAngle -= startAngle;
    startAngle = 0;

    deg += adj;

    if(deg >= 360)
    {
        deg -= 360;
    }

    // calculate the percent
    startAngle = 0;

    if(deg < 0)
        return LE_FALSE;

    if(deg > endAngle)
        return LE_FALSE;

    if(_this->spanAngle < 0)
    {
        *percent = 100 - lePercentWholeRounded(deg, endAngle);
    }
    else
    {
        *percent = lePercentWholeRounded(deg, endAngle);
    }

    return LE_TRUE;
}

static leBool _pointInActiveArc(leCircularSliderWidget* _this,
                                lePoint pt)
{

    uint32_t innerRadius;
    uint32_t outerRadius;
    lePoint centerPt;
    uint32_t vecLength;
    uint32_t percent;

    // test the radius
    innerRadius = _this->radius - _this->activeArc.thickness / 2;
    outerRadius = _this->radius + _this->activeArc.thickness / 2;

    centerPt.x = _this->widget.rect.width / 2;
    centerPt.y = _this->widget.rect.height / 2;

    pt.x -= centerPt.x;
    pt.y -= centerPt.y;

    vecLength = (pt.x * pt.x) + (pt.y * pt.y);

    innerRadius *= innerRadius;
    outerRadius *= outerRadius;

    if(vecLength < innerRadius || vecLength > outerRadius)
        return LE_FALSE;

    return getPercent(_this, pt, &percent);
}

static leBool _pointInButton(leCircularSliderWidget* _this,
                             lePoint pt)
{
    lePoint centerPt;
    lePoint btnPnt;

    uint32_t deg = leDegreesFromPercent(_this->value,
                                        _this->spanAngle,
                                        _this->startAngle);

    lePointOnCircle(_this->radius,
                    deg,
                    &btnPnt);

    btnPnt.y *= -1;

    centerPt.x = _this->widget.rect.width / 2;
    centerPt.y = _this->widget.rect.height / 2;

    btnPnt.x += centerPt.x;
    btnPnt.y += centerPt.y;

    btnPnt.x -= pt.x;
    btnPnt.y -= pt.y;

    if((btnPnt.x * btnPnt.x + btnPnt.y * btnPnt.y) < (int32_t)(_this->circleButtonArc.radius * _this->circleButtonArc.radius))
    {
        return LE_TRUE;
    }

    return LE_FALSE;
}

static void handleTouchDownEvent(leCircularSliderWidget* _this,
                                 leWidgetEvent_TouchDown* evt)
{
    lePoint pnt;
    uint32_t percent;

    LE_ASSERT_THIS();

    pnt.x = evt->x;
    pnt.y = evt->y;

    if(_this->circleButtonArc.visible == LE_FALSE)
        return;

    leUtils_PointScreenToLocalSpace((leWidget*)_this, &pnt);

    if(_pointInButton(_this, pnt) == LE_FALSE)
    {
        if(_this->buttonTouch == LE_FALSE && _pointInActiveArc(_this, pnt) == LE_FALSE)
        {
            _this->btnState = LE_CIRCULAR_SLIDER_STATE_UP;

            return;
        }
    }

    if(getPercent(_this, pnt, &percent) == LE_FALSE)
        return;

    _this->fn->setValue(_this, percent);

    _this->btnState = LE_CIRCULAR_SLIDER_STATE_DOWN;

    if (_this->pressedCallback != NULL)
    {
        _this->pressedCallback(_this, _this->value);
    }

    leWidgetEvent_Accept((leWidgetEvent*)evt, (leWidget*)_this);
}

static void handleTouchUpEvent(leCircularSliderWidget* _this,
                               leWidgetEvent_TouchUp* evt)
{
    LE_ASSERT_THIS();
    
    if (_this->btnState == LE_CIRCULAR_SLIDER_STATE_DOWN)
    {
        leWidgetEvent_Accept((leWidgetEvent*)evt, (leWidget*)_this);
        
        _this->btnState = LE_CIRCULAR_SLIDER_STATE_UP;

        if(_this->sticky == LE_TRUE)
        {
            _this->fn->setValue(_this,
                                _calculateSnapValue(_this->snapDivisions,
                                                    _this->value));
        }
    
        if (_this->releasedCallback != NULL)
        {
            _this->releasedCallback(_this, _this->value);
        }
    }
}

static void handleTouchMovedEvent(leCircularSliderWidget* _this, leWidgetEvent_TouchMove* evt)
{
    lePoint pnt;
    uint32_t percent;

    if(_this->btnState != LE_CIRCULAR_SLIDER_STATE_DOWN)
        return;

    pnt.x = evt->x;
    pnt.y = evt->y;

    if(_this->circleButtonArc.visible == LE_FALSE)
        return;

    leUtils_PointScreenToLocalSpace((leWidget*)_this, &pnt);

    if(_pointInButton(_this, pnt) == LE_FALSE)
    {
        if(_this->buttonTouch == LE_FALSE && _pointInActiveArc(_this, pnt) == LE_FALSE)
        {
            _this->btnState = LE_CIRCULAR_SLIDER_STATE_UP;

            return;
        }
    }

    if(getPercent(_this, pnt, &percent) == LE_FALSE)
        return;

    _this->fn->setValue(_this, percent);

    _this->btnState = LE_CIRCULAR_SLIDER_STATE_DOWN;

    if (_this->pressedCallback != NULL)
    {
        _this->pressedCallback(_this, _this->value);
    }

    leWidgetEvent_Accept((leWidgetEvent*)evt, (leWidget*)_this);
}

void _leCircularSliderWidget_Paint(leCircularSliderWidget* _this);

#if LE_DYNAMIC_VTABLES == 1
void _leWidget_FillVTable(leWidgetVTable* tbl);

void _leCircularSliderWidget_GenerateVTable(void)
{
    _leWidget_FillVTable((void*)&circularSliderWidgetVTable);
    
    /* overrides from base class */
    circularSliderWidgetVTable._destructor = _leCircularSliderWidget_Destructor;
    circularSliderWidgetVTable._paint = _leCircularSliderWidget_Paint;
    circularSliderWidgetVTable.touchDownEvent = handleTouchDownEvent;
    circularSliderWidgetVTable.touchUpEvent = handleTouchUpEvent;
    circularSliderWidgetVTable.touchMoveEvent = handleTouchMovedEvent;
    
    /* member functions */
    circularSliderWidgetVTable.getRadius = getRadius;
    circularSliderWidgetVTable.setRadius = setRadius;
    circularSliderWidgetVTable.getStartAngle = getStartAngle;
    circularSliderWidgetVTable.setStartAngle = setStartAngle;
    circularSliderWidgetVTable.getSpanAngle = getSpanAngle;
    circularSliderWidgetVTable.setSpanAngle = setSpanAngle;
    circularSliderWidgetVTable.getArcThickness = getArcThickness;
    circularSliderWidgetVTable.setArcThickness = setArcThickness;
    circularSliderWidgetVTable.getArcRadius = getArcRadius;
    circularSliderWidgetVTable.setArcRadius = setArcRadius;
    circularSliderWidgetVTable.getArcScheme = getArcScheme;
    circularSliderWidgetVTable.setArcScheme = setArcScheme;
    circularSliderWidgetVTable.getArcVisible = getArcVisible;
    circularSliderWidgetVTable.setArcVisible = setArcVisible;
    circularSliderWidgetVTable.getValue = getValue;
    circularSliderWidgetVTable.setValue = setValue;
    circularSliderWidgetVTable.getRoundEdges = getRoundEdges;
    circularSliderWidgetVTable.setRoundEdges = setRoundEdges;
    circularSliderWidgetVTable.getStickyButton = getStickyButton;
    circularSliderWidgetVTable.setStickyButton = setStickyButton;
    circularSliderWidgetVTable.getSnapDivisions = getSnapDivisions;
    circularSliderWidgetVTable.setSnapDivisions = setSnapDivisions;
    circularSliderWidgetVTable.getTouchOnButtonOnly = getTouchOnButtonOnly;
    circularSliderWidgetVTable.setTouchOnButtonOnly = setTouchOnButtonOnly;
    circularSliderWidgetVTable.getDirection = getDirection;
    circularSliderWidgetVTable.setDirection = setDirection;
    circularSliderWidgetVTable.setPressedEventCallback = setPressedEventCallback;
    circularSliderWidgetVTable.setValueChangedEventCallback = setValueChangedEventCallback;
    circularSliderWidgetVTable.setReleasedEventCallback = setReleasedEventCallback;
}

void _leCircularSliderWidget_FillVTable(leCircularSliderWidgetVTable* tbl)
{
    *tbl = circularSliderWidgetVTable;
}
#else
static const leCircularSliderWidgetVTable circularSliderWidgetVTable =
{
    // base class
    .getType = (void*)_leWidget_GetType,
    .getX = (void*)_leWidget_GetX,
    .setX = (void*)_leWidget_SetX,
    .getY = (void*)_leWidget_GetY,
    .setY = (void*)_leWidget_SetY,
    .setPosition = (void*)_leWidget_SetPosition,
    .translate = (void*)_leWidget_Translate,
    .getWidth = (void*)_leWidget_GetWidth,
    .setWidth = (void*)_leWidget_SetWidth,
    .getHeight = (void*)_leWidget_GetHeight,
    .setHeight = (void*)_leWidget_SetHeight,
    .setSize = (void*)_leWidget_SetSize,
    .resize = (void*)_leWidget_Resize,
    .getAlphaEnabled = (void*)_leWidget_GetAlphaEnabled,
    .getCumulativeAlphaEnabled = (void*)_leWidget_GetCumulativeAlphaEnabled,
    .setAlphaEnabled = (void*)_leWidget_SetAlphaEnabled,
    .getAlphaAmount = (void*)_leWidget_GetAlphaAmount,
    .getCumulativeAlphaAmount = (void*)_leWidget_GetCumulativeAlphaAmount,
    .setAlphaAmount = (void*)_leWidget_SetAlphaAmount,
    .isOpaque = (void*)_leWidget_IsOpaque,
    .getEnabled = (void*)_leWidget_GetEnabled,
    .setEnabled = (void*)_leWidget_SetEnabled,
    .getVisible = (void*)_leWidget_GetVisible,
    .setVisible = (void*)_leWidget_SetVisible,
    .localRect = (void*)_leWidget_LocalRect,
    .rectToParent = (void*)_leWidget_RectToParentSpace,
    .rectToScreen = (void*)_leWidget_RectToScreenSpace,
    .addChild = (void*)_leWidget_AddChild,
    .removeChild = (void*)_leWidget_RemoveChild,
    .removeAllChildren = (void*)_leWidget_RemoveAllChildren,
    .getRootWidget = (void*)_leWidget_GetRootWidget,
    .setParent = (void*)_leWidget_SetParent,
    .getChildCount = (void*)_leWidget_GetChildCount,
    .getChildAtIndex = (void*)_leWidget_GetChildAtIndex,
    .getIndexOfChild = (void*)_leWidget_GetIndexOfChild,
    .containsDescendant = (void*)_leWidget_ContainsDescendant,
    .getScheme = (void*)_leWidget_GetScheme,
    .setScheme = (void*)_leWidget_SetScheme,
    .getBorderType = (void*)_leWidget_GetBorderType,
    .setBorderType = (void*)_leWidget_SetBorderType,
    .getBackgroundType = (void*)_leWidget_GetBackgroundType,
    .setBackgroundType = (void*)_leWidget_SetBackgroundType,
    .getHAlignment = (void*)_leWidget_GetHAlignment,
    .setHAlignment = (void*)_leWidget_SetHAlignment,
    .getVAlignment = (void*)_leWidget_GetVAlignment,
    .setVAlignment = (void*)_leWidget_SetVAlignment,
    .getMargins = (void*)_leWidget_GetMargins,
    .setMargins = (void*)_leWidget_SetMargins,
    .getCornerRadius = (void*)_leWidget_GetCornerRadius,
    .setCornerRadius = (void*)_leWidget_SetCornerRadius,
    .hasFocus = (void*)_leWidget_HasFocus,
    .setFocus = (void*)_leWidget_SetFocus,
    .invalidate = (void*)_leWidget_Invalidate,
    .invalidateContents = (void*)_leWidget_InvalidateContents,
    .installEventFilter = (void*)_leWidget_InstallEventFilter,
    .removeEventFilter = (void*)_leWidget_RemoveEventFilter,

    .update = (void*)_leWidget_Update,

    .moveEvent = (void*)_leWidget_MoveEvent,
    .resizeEvent = (void*)_leWidget_ResizeEvent,
    .focusLostEvent = (void*)_leWidget_FocusLostEvent,
    .focusGainedEvent = (void*)_leWidget_FocusGainedEvent,
    .languageChangeEvent = (void*)_leWidget_LanguageChangeEvent,

    ._handleEvent = (void*)_leWidget_HandleEvent,
    ._validateChildren = (void*)_leWidget_ValidateChildren,
    ._increaseDirtyState = (void*)_leWidget_IncreaseDirtyState,
    ._setDirtyState = (void*)_leWidget_SetDirtyState,
    ._clearDirtyState = (void*)_leWidget_ClearDirtyState,
    ._invalidateBorderAreas = (void*)_leWidget_InvalidateBorderAreas,
    ._damageArea = (void*)_leWidget_DamageArea,

    /* overrides from base class */
    ._destructor = _leCircularSliderWidget_Destructor,
    ._paint = _leCircularSliderWidget_Paint,
    .touchDownEvent = handleTouchDownEvent,
    .touchUpEvent = handleTouchUpEvent,
    .touchMoveEvent = handleTouchMovedEvent,

    /* member functions */
    .getRadius = getRadius,
    .setRadius = setRadius,
    .getStartAngle = getStartAngle,
    .setStartAngle = setStartAngle,
    .getSpanAngle = getSpanAngle,
    .setSpanAngle = setSpanAngle,
    .getArcThickness = getArcThickness,
    .setArcThickness = setArcThickness,
    .getArcRadius = getArcRadius,
    .setArcRadius = setArcRadius,
    .getArcScheme = getArcScheme,
    .setArcScheme = setArcScheme,
    .getArcVisible = getArcVisible,
    .setArcVisible = setArcVisible,
    .getValue = getValue,
    .setValue = setValue,
    .getRoundEdges = getRoundEdges,
    .setRoundEdges = setRoundEdges,
    .getStickyButton = getStickyButton,
    .setStickyButton = setStickyButton,
    .getSnapDivisions = getSnapDivisions,
    .setSnapDivisions = setSnapDivisions,
    .getTouchOnButtonOnly = getTouchOnButtonOnly,
    .setTouchOnButtonOnly = setTouchOnButtonOnly,
    .getDirection = getDirection,
    .setDirection = setDirection,
    .setPressedEventCallback = setPressedEventCallback,
    .setValueChangedEventCallback = setValueChangedEventCallback,
    .setReleasedEventCallback = setReleasedEventCallback,
};
#endif

#endif // LE_CIRCULARSLIDER_WIDGET_ENABLED
